﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Text.RegularExpressions;
using EzyThirdLogic.ExtensionMethods;

namespace EzyThirdLogic
{
    public class FunctionalDependency : INotifyPropertyChanged, IComparable
    {
        #region Private Field

        private string _stringDeterminant; // string representation of left side of the FD, e.g: A,B,C
        private string _stringDependent; // string representation of right side of the FD, e.g: B,C,D

        private BitArray _bitDeterminant; // bit representation of left side of the FD, e.g: 1001111
        private BitArray _bitDependent; // bit representation of left side of the FD, e.g: 1001111

        #endregion

        #region Constants

        const int BitArrayLength = 26;

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Constructor

        public FunctionalDependency()
        {
        }

        public FunctionalDependency(string determinant, string dependent, int max = 26)
        {
            _stringDeterminant = determinant;
            _stringDependent = dependent;

            // set bit determinant
            SetBitArray(ref _bitDeterminant, determinant, max);

            // set bit dependent
            SetBitArray(ref _bitDependent, dependent, max);
        }

        #endregion

        #region Properties

        public string Determinant 
        {
            get 
            { 
                return _stringDeterminant; 
            }
            set
            {
                _stringDeterminant = value;
                this.NotifyPropertyChanged("Determinant");
            }
        }

        public string Dependent 
        {
            get
            {
                return _stringDependent;
            }
            set
            {
                _stringDependent = value;
                this.NotifyPropertyChanged("Dependent");
            }
        }

        public BitArray BitDeterminant
        {
            get 
            {
                if (_bitDeterminant == null)
                {
                    // set bit determinant
                    SetBitArray(ref _bitDeterminant, _stringDeterminant);
                }
                return _bitDeterminant; 
            }
            set { _bitDeterminant = value; }
        }

        public BitArray BitDependent
        {
            get 
            {
                if (_bitDependent == null)
                {
                    // set bit determinant
                    SetBitArray(ref _bitDependent, _stringDependent);
                }
                return _bitDependent; 
            }
            set { _bitDependent = value; }
        }

        #endregion

        #region Public Methods

        public bool DeterminantIsEmpty()
        {
            return string.IsNullOrEmpty(Determinant);
        }

        public bool DependentIsEmpty()
        {
            return string.IsNullOrEmpty(Dependent);
        }

        public bool IsTrivialFunctionalDependency()
        {
            var determinantAttributes = Determinant.Split(Convert.ToChar(",")).ToList<string>();
            for (int i = 0; i < determinantAttributes.Count; i++)
            {
                determinantAttributes[i] = determinantAttributes[i].Trim();
            }
            determinantAttributes.Sort();
            var determinant = "";
            foreach (var attr in determinantAttributes)
            {
                determinant += attr.Trim();
            }

            var dependentAttributes = Dependent.Split(Convert.ToChar(",")).ToList<string>();
            for (int i = 0; i < dependentAttributes.Count; i++)
            {
                dependentAttributes[i] = dependentAttributes[i].Trim();
            }
            dependentAttributes.Sort();
            var dependent = "";
            foreach (var attr in dependentAttributes)
            {
                dependent += attr.Trim();
            }

            return determinant.Contains(dependent);
        }

        public bool DeterminantContainsInvalidAttributes()
        {
            // get the array of attributes
            string[] determinantAttributes = Determinant.Split(Convert.ToChar(","));

            // go through each attribute to validate
            foreach (var attribute in determinantAttributes)
            {
                if (attribute.Length > 1 ||
                    !Regex.IsMatch(attribute.ToString(), @"[A-Z]") ||
                    char.IsLower(Convert.ToChar(attribute.ToString())))
                {
                    return true;
                }
            }

            return false;
        }

        public bool DependentContainsInvalidAttributes()
        {
            // get the array of attributes
            string[] dependentAttributes = Dependent.Split(Convert.ToChar(","));

            // go through each attribute to validate
            foreach (var attribute in dependentAttributes)
            {
                if (attribute.Length > 1 ||
                    !Regex.IsMatch(attribute.ToString(), @"[A-Z]") ||
                    char.IsLower(Convert.ToChar(attribute.ToString())))
                {
                    return true;
                }
            }

            return false;
        }

        public string LeftToString()
        {
            string str = string.Empty;
            int diff;
            char c;

            for (int i = 0; i < this.BitDeterminant.LogicalSize(); )
            {
                int setid = this.BitDeterminant.NextSetBit(i);
                diff = setid + 65;
                c = (char)diff;
                str += c;
                i = setid + 1;
            }

            return str;
        }

        public string RightToString()
        {
            string str = string.Empty;
            int diff;
            char c;

            for (int i = 0; i < this.BitDependent.LogicalSize(); )
            {
                int setid = this.BitDependent.NextSetBit(i);
                diff = setid + 65;
                c = (char)diff;
                str += c;
                i = setid + 1;
            }

            return str;
        }

        public String LeftToStringWithDelimiter()
        {
            string str = string.Empty;
            str = ToStringWithDelimiter(this.BitDeterminant);
            return str;
        }

        public String RightToStringWithDelimiter()
        {
            string str = string.Empty;
            str = ToStringWithDelimiter(this.BitDependent);
            return str;
        }

        public static String ToStringWithDelimiter(BitArray bitset)
        {
            string str = string.Empty;
            int diff;
            char c;

            for (int i = 0; i < bitset.LogicalSize(); )
            {
                int setid = bitset.NextSetBit(i);
                diff = setid + 65;
                c = (char)diff;
                str += c;
                str += ",";
                i = setid + 1;
            }

            return str;
        }

        //convert one character into bitArray
        public static BitArray ToBitArray(char character, int size = 26)
        {
            var bitArr = new BitArray(size);
            int id = character - 'A';
            if (id >= size)
            {
                throw new Exception("Invalid Attribute");
            }
            bitArr[id] = !bitArr[id];
            return bitArr;
        }

        public int CompareTo(object fd)
        {
            if (this.BitDeterminant.IsEquals((fd as FunctionalDependency).BitDeterminant) &&
                this.BitDependent.IsEquals((fd as FunctionalDependency).BitDependent))
                return 0;
            return this.ToString().CompareTo(fd.ToString());
        }

        public override string ToString()
        {
            return LeftToString() + " -> " + RightToString();
        }

        #endregion

        #region Private Methods

        private void NotifyPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        /// <summary>
        /// Convert a string of attributes into a bit array
        /// e.g: "A,B,C" into 111 - true true true-
        /// </summary>
        /// <param name="bitArray"></param>
        /// <param name="str"></param>
        private void SetBitArray(ref BitArray bitArrayAttributes, string strAttributes, int max = 26)
        {
            // parse the string of attributes into array of attributes
            string[] arrAttributes = strAttributes.Split(Convert.ToChar(","));

            // initialize bit array attributes
            bitArrayAttributes = new BitArray(BitArrayLength);

            // convert to bit array
            foreach (string attribute in arrAttributes)
            {
                if (string.IsNullOrEmpty(attribute.Trim()))
                {
                    continue;
                }

                // get the first character in the attributes
                char firstChar = Convert.ToChar(attribute.Substring(0, 1));

                // get the bit array from the character
                BitArray bitArr = ToBitArray(firstChar, max);

                // add to the FD bit array
                bitArrayAttributes.Or(bitArr);
            }
        }
        #endregion       
    
        public void UpdateBitFromString()
        {
            SetBitArray(ref _bitDeterminant, _stringDeterminant);
            SetBitArray(ref _bitDependent, _stringDependent);
        }
    }
}
